using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace cb0tServer
{
    class UserObject
    {
        // user variables
        internal int captcha_attempts = 0;
        internal int captcha_answer = 0;
        internal bool has_captched = false;
        internal EncryptionMode EncMode = EncryptionMode.Unencrypted;
        internal byte EncByte = 0;
        internal ushort EncWord = 0;


        public List<String> ignores = new List<String>();
        public Guid guid;
        public ushort files;
        public ushort dc_port;
        public IPAddress node_ip;
        public ushort node_port;
        public uint line;
        public String name;
        public String version;
        public IPAddress local_ip;
        public IPAddress external_ip;
        public bool browse;
        public bool custom_client;
        public byte current_uploads;
        public byte max_uploads;
        public byte current_queued;
        public IPAddress physical_ip;
        public bool logged_in;
        public ushort id;
        public ushort vroom;
        public bool admin;
        public bool host;
        public String orgName;
        public bool customname;
        public String customname_text;
        public bool muzzled;
        public bool kiddied;
        public bool bansend = false;
        public bool echo = false;
        public String echo_text = String.Empty;
        public bool antiflood = false;
        public bool painted = false;
        public String painted_text = String.Empty;
        public bool ipsend = false;
        public bool idle = false;
        public long idle_time = 0;
        public bool translate = false;
        public int translate_type = 0;
        public bool unicode = false;
        public bool lowered = false;
        public bool vspy = false;
        public int login_attempts = 0;
        public bool logsend = false;
        public bool cloaked = false;
        public byte level = 0;
        public uint admincookie = Helpers.AdminCookie();
        public bool supports_compression = false;
        public byte user_age = 0;
        public byte user_sex = 0;
        public byte user_country = 0;
        public String user_region = String.Empty;
        public String user_message = String.Empty;
        public byte[] user_image = null;
        public int avatar_count = 0;
        public bool got_av_once = false;
        public bool got_personal_once = false;
        public bool pm_spam_tested = false;
        public bool connect4 = false;
        public UserFont font = null;
        public bool voice_chat = false;
        public bool voice_private_chat = false;
        public List<String> voice_chat_ignores = new List<String>();
        public List<String> custom_tags = new List<String>();

        // socket variables
        public Socket socket = null;
        public int session_spawn_time = 0;
        public int receive_fails = 0;
        private uint last_packet_time = 0;
        private uint packet_counter_main = 0;
        private uint packet_counter_pm = 0;
        private uint packet_counter_misc = 0;
        public int file_count = 0;
        public int last_update_time = 0;
        public int files_checked = 0;
        public bool got_dns = false;
        public String dns_value = null;
        public bool auto_redirect = false;

        private List<byte> inbound_buffer = new List<byte>(); // for dumping received data
        private Queue<byte[]> outbound_buffer = new Queue<byte[]>(); // for queuing packets
        private List<String> recent_posts = new List<String>(); // anti repetition flooding
        private List<SharedFileObject> shared_files = new List<SharedFileObject>(); // file browse
        
        public List<PingUserObject> pings = new List<PingUserObject>(); // users pings

        public bool supports_custom_emotes = false;
        public List<CustomEmoteItem> custom_emotes = new List<CustomEmoteItem>();

        public UserObject() { this.avatar_count = 0; }

        public UserObject(Socket socket, int id, int session_spawn_time)
        {
            this.avatar_count = 0;
            this.socket = socket;
            this.socket.Blocking = false;
            this.physical_ip = ((IPEndPoint)this.socket.RemoteEndPoint).Address; // extract TRUE ip
            this.logged_in = false;
            this.id = (ushort)id;
            this.session_spawn_time = session_spawn_time;
            this.last_packet_time = (uint)session_spawn_time;

            try
            {
                Dns.BeginGetHostEntry(this.physical_ip, new AsyncCallback(this.DnsReceived), null);
            }
            catch { }
        }

        private void DnsReceived(IAsyncResult args)
        {
            try
            {
                IPHostEntry result = Dns.EndGetHostEntry(args);
                this.dns_value = result.HostName;
            }
            catch { this.dns_value = this.physical_ip.ToString(); }
        }

        public void AddReceivedBytes(byte[] data) // add to received data dump
        {
            this.inbound_buffer.AddRange(data);
        }

        public void RemoveReceivedBytes(int count) // trim received data dump
        {
            this.inbound_buffer.RemoveRange(0, count);
        }

        public byte[] GetReceivedBytes() // retrieve received data dump
        {
            if (this.inbound_buffer.Count > 3)
            {
                return this.inbound_buffer.ToArray();
            }

            return null;
        }

        public void SendOutboundPacket(byte[] data) // add packet to outbound buffer
        {
            this.outbound_buffer.Enqueue(data);
        }

        public void DispatchOutboundPackets() // send all pending packets
        {
            while (this.outbound_buffer.Count > 0)
            {
                try
                {
                    byte[] buf = this.outbound_buffer.Peek();

                    if (this.EncMode == EncryptionMode.Encrypted)
                        buf = ServerOutboundPackets.Encrypt(buf, this.EncByte, this.EncWord);

                    this.socket.Send(buf);
                    Stats.BytesSent += (ulong)this.outbound_buffer.Dequeue().Length;
                }
                catch { return; }
            }
        }

        public void Disconnect() // kill user
        {
            this.logged_in = false;
            this.shared_files.Clear();
            this.pings.Clear();
            this.DispatchOutboundPackets(); // try to send outstanding packets

            try { this.socket.Disconnect(false); }
            catch { }

            try { this.socket.Shutdown(SocketShutdown.Both); }
            catch { }

            try { this.socket.Close(); }
            catch { }

            this.socket = null; // delete socket
        }

        public void UserWasLinkLeaf() // server <-> server linking
        {
            this.socket = null;
        }

        public bool IsSet() // socket is occupied?
        {
            return this.socket != null;
        }

        public void AddFile(SharedFileObject file) // submit add share
        {
            this.shared_files.Add(file);
        }

        public SharedFileObject[] GetFiles() // get all files
        {
            if (this.shared_files.Count == 0) return null;
            return this.shared_files.ToArray();
        }

        public bool IsFlooding(byte msg, byte[] data, uint time)
        {
            if (msg == 10 || msg == 11) // check text repetition
            {
                if (Helpers.IsTextFlood(this))
                {
                    this.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket("\x000314Flood: " + this.name + " text flood"));
                    UserPool.BroadcastToAdmins(ServerOutboundPackets.AnnoucePacket("\x000314Flood: " + this.name + " text flood"));
                    return true;
                }

                if (this.level > 0)
                    return false;

                this.recent_posts.Insert(0, Encoding.UTF8.GetString(data));

                if (this.recent_posts.Count == 5)
                {
                    this.recent_posts.RemoveAt(4);
                    bool is_repeating = this.recent_posts.TrueForAll(delegate(String str) { return str == this.recent_posts[0]; });

                    if (is_repeating)
                    {
                        this.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket("\x000314Flood: " + this.name + " text flood"));
                        UserPool.BroadcastToAdmins(ServerOutboundPackets.AnnoucePacket("\x000314Flood: " + this.name + " text flood"));
                        return true;
                    }
                }
            }

            if (this.level > 0)
                return false;

            if (msg == 50 || msg == 80 || msg == 250)
                return false;

            if (msg == 203)
                if (data[0] == 0)
                    return false;

            if (time > (this.last_packet_time + 1)) // been over 2 seconds since last packet so user isn't flooding
            {
                this.last_packet_time = time;
                this.packet_counter_main = 0;
                this.packet_counter_misc = 0;
                this.packet_counter_pm = 0;
            }
            else // these tolerances seem to work quite well
            {
                if (msg == 10 || msg == 11) // 3 text posts every 2 seconds
                {
                    this.packet_counter_main++;

                    if (this.packet_counter_main > 3)
                    {
                        this.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket("\x000314Flood: " + this.name + " text flood"));
                        UserPool.BroadcastToAdmins(ServerOutboundPackets.AnnoucePacket("\x000314Flood: " + this.name + " text flood"));
                        return true;
                    }

                    return false;
                }

                if (msg == 25) // 7 pm posts every 2 seconds (some people have long pm auto replys)
                {
                    this.packet_counter_pm++;

                    if (this.packet_counter_pm > 7)
                    {
                        this.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket("\x000314Flood: " + this.name + " pm flood"));
                        UserPool.BroadcastToAdmins(ServerOutboundPackets.AnnoucePacket("\x000314Flood: " + this.name + " pm flood"));
                        return true;
                    }

                    return false;
                }

                this.packet_counter_misc++;

                if (this.packet_counter_misc > 8)
                {
                    if (this.logged_in)
                    {
                        this.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket("\x000314Flood: " + this.name + " pid " + msg));
                        UserPool.BroadcastToAdmins(ServerOutboundPackets.AnnoucePacket("\x000314Flood: " + this.name + " pid " + msg));
                    }

                    return true;
                }
            }

            return false;
        }
    }

    class UserFont
    {
        public String name;
        public int size;
        public byte ncol;
        public byte tcol;
        public byte[] name_ex;
        public byte[] text_ex;

        public UserFont(String name, int size)
        {
            this.name = name;
            this.size = size;
            this.ncol = 255;
            this.tcol = 255;
        }
    }

    class CustomEmoteItem
    {
        public String Shortcut;
        public byte Size;
        public byte[] Image;
    }
}
